package com.ayu.shortlink.settlement.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson2.JSON;
import com.alipay.api.internal.util.AlipaySignature;
import com.ayu.shortlink.settlement.common.biz.user.UserContext;
import com.ayu.shortlink.settlement.common.constant.RedisCacheConstant;
import com.ayu.shortlink.settlement.common.convention.exception.ServiceException;
import com.ayu.shortlink.settlement.common.enums.TradeStatusEnum;
import com.ayu.shortlink.settlement.common.enums.UserCouponStatusEnum;
import com.ayu.shortlink.settlement.config.AliPayProperties;
import com.ayu.shortlink.settlement.dao.entity.PayDO;
import com.ayu.shortlink.settlement.dao.entity.UserCouponDO;
import com.ayu.shortlink.settlement.dao.mapper.CouponTemplateMapper;
import com.ayu.shortlink.settlement.dao.mapper.PayMapper;
import com.ayu.shortlink.settlement.dao.mapper.UserCouponMapper;
import com.ayu.shortlink.settlement.dto.base.PayRequest;
import com.ayu.shortlink.settlement.dto.resp.PayRespDTO;
import com.ayu.shortlink.settlement.service.UserPayService;
import com.ayu.shortlink.settlement.service.bases.strategy.AbstractPayCreateOrderStrategyContext;
import com.ayu.shortlink.settlement.service.handler.strategy.DiscountStrategyContext;
import com.ayu.shortlink.settlement.util.GeneratorIdManager;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.baomidou.mybatisplus.extension.toolkit.SqlHelper;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * 用户支付实现层
 */
@Service
@RequiredArgsConstructor
@Slf4j
public class UserPayServiceImpl extends ServiceImpl<PayMapper, PayDO> implements UserPayService {
    private final StringRedisTemplate stringRedisTemplate;
    private final AbstractPayCreateOrderStrategyContext abstractPayCreateOrderStrategyContext;
    private final GeneratorIdManager generatorIdManager;
    private final PayMapper payMapper;
    private final CouponTemplateMapper couponTemplateMapper;
    private final UserCouponMapper userCouponMapper;
    private final Long NO_COUPON = -1L;
    private final DiscountStrategyContext discountStrategyContext;
    private final AliPayProperties alipayProperties;

    @SneakyThrows
    @Override
    @Transactional(rollbackFor = Exception.class)
    public PayRespDTO createUserPay(PayRequest requestParam) {
        // 存在一种可能 该订单用户之前已经创建 所以要去缓存看一下
        String ORDER_PAY_INFO_KEY = String.format(RedisCacheConstant.ORDER_PAY_INFO, requestParam.getOrderSn());
        PayRespDTO ret = JSON.parseObject(stringRedisTemplate.opsForValue().get(ORDER_PAY_INFO_KEY), PayRespDTO.class);
        if (ret != null){
            return ret;
        }

        // 通过支付渠道创建相应订单
        ret = abstractPayCreateOrderStrategyContext.chooseAndExecuteResp(requestParam.buildMark(), requestParam);

        PayDO payDO = BeanUtil.toBean(ret, PayDO.class);
        // 去设置唯一的流水号
        payDO.setPaySn(generatorIdManager.generatorID());
        // 设置交易总额
        payDO.setPayAmount(requestParam.getTotalAmount());
        // 先设置订单状态为待支付
        payDO.setStatus(TradeStatusEnum.WAIT_BUYER_PAY.tradeCode());
        // 如果选择了优惠券
        if (requestParam.getCouponTemplateId() != null) {
            payDO.setCouponTemplateId(requestParam.getCouponTemplateId());

            // 将优惠券设置为锁定状态-LOCK
            LambdaUpdateWrapper<UserCouponDO> updateWrapper = Wrappers.lambdaUpdate(UserCouponDO.class)
                    .eq(UserCouponDO::getUserId, UserContext.getUserId())
                    .eq(UserCouponDO::getId, requestParam.getCouponTemplateId())
                    .set(UserCouponDO::getStatus, UserCouponStatusEnum.LOCKING);
            int update = userCouponMapper.update(updateWrapper);

            if (!SqlHelper.retBool(update)){
                log.error("优惠券更新失败，优惠券ID：{}",requestParam.getCouponTemplateId());
                throw new ServiceException("支付单创建失败");
            }
        }

        int res = payMapper.insert(payDO);
        if (!SqlHelper.retBool(res)){
            log.error("支付单创建失败，支付聚合根：{}", JSON.toJSONString(requestParam));
            throw new ServiceException("支付单创建失败");
        }
        //设置30分钟缓存
        stringRedisTemplate.opsForValue().set(ORDER_PAY_INFO_KEY, JSON.toJSONString(payDO),30, TimeUnit.MINUTES);
        //TODO 后面需要检查 也需要去发送定时任务让订单失效
        return BeanUtil.toBean(payDO, PayRespDTO.class);
    }

    /**
     * 支付宝回调信息：
     *         String notifyTime = params.get("notify_time");        // 通知时间
     *         String notifyType = params.get("notify_type");        // 通知类型
     *         String notifyId = params.get("notify_id");            // 通知校验ID
     *         String appId = params.get("app_id");                  // 支付宝分配给开发者的应用ID
     *         String charset = params.get("charset");               // 编码格式
     *         String version = params.get("version");               // 接口版本
     *         String signType = params.get("sign_type");            // 签名类型
     *         String sign = params.get("sign");                     // 签名
     *         String tradeNo = params.get("trade_no");             // 支付宝交易号
     *         String outTradeNo = params.get("out_trade_no");      // 商户订单号
     *         String outBizNo = params.get("out_biz_no");          // 商户业务号
     *         String buyerId = params.get("buyer_id");             // 买家支付宝用户号
     *         String buyerLogonId = params.get("buyer_logon_id");  // 买家支付宝账号
     *         String sellerId = params.get("seller_id");           // 卖家支付宝用户号
     *         String sellerEmail = params.get("seller_email");     // 卖家支付宝账号
     *         String tradeStatus = params.get("trade_status");     // 交易状态
     *         String totalAmount = params.get("total_amount");     // 订单金额
     *         String receiptAmount = params.get("receipt_amount"); // 实收金额
     *         String invoiceAmount = params.get("invoice_amount"); // 开票金额
     *         String buyerPayAmount = params.get("buyer_pay_amount"); // 付款金额
     *         String pointAmount = params.get("point_amount");     // 集分宝金额
     *         String refundFee = params.get("refund_fee");        // 总退款金额
     *         String subject = params.get("subject");              // 订单标题
     *         String body = params.get("body");                    // 商品描述
     *         String gmtCreate = params.get("gmt_create");        // 交易创建时间
     *         String gmtPayment = params.get("gmt_payment");      // 交易付款时间
     *         String gmtRefund = params.get("gmt_refund");        // 交易退款时间
     *         String gmtClose = params.get("gmt_close");          // 交易结束时间
     *         String fundBillList = params.get("fund_bill_list"); // 支付金额信息
     *         String passbackParams = params.get("passback_params"); // 回传参数
     *  Tips:如果return failure 那么会触发支付宝的重试机制
     *          重试频率：15s/15s/30s/3m/10m/20m/30m/30m/30m/60m/3h/3h/3h/6h/6h
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public String alipayNotify(HttpServletRequest request) {
        Map<String, String> params = new HashMap<>();
        Map<String, String[]> requestParams = request.getParameterMap();
        for (String name : requestParams.keySet()) {
            params.put(name, request.getParameter(name));
        }

        try {
            boolean signVerified = AlipaySignature.rsaCheckV1(
                    params,
                    alipayProperties.getAlipayPublicKey(),
                    alipayProperties.getCharset(),
                    alipayProperties.getSignType()
            );
            // 验签失败
            if (!signVerified){
                log.error("支付宝回调验签失败，params：{}",JSON.toJSONString(params));
                return "success";
            }

            // 验签成功，处理业务逻辑
            String tradeStatus = params.get("trade_status");

            String orderSn = params.get("out_trade_no");
            // 更新订单状态AND优惠券核销
            LambdaQueryWrapper<PayDO> queryWrapper = Wrappers.lambdaQuery(PayDO.class)
                    .eq(PayDO::getOrderSn, orderSn);
            PayDO payDO = payMapper.selectOne(queryWrapper);

            // 直接返回success 防止重试
            if (payDO == null){
                log.error("支付单不存在,orderSn:{}", orderSn);
                return "success";
            }

            // 订单已经是终态，直接返回success
            if (payDO.getStatus().equals(TradeStatusEnum.TRADE_SUCCESS.tradeCode())){
                log.info("订单已处理完成，订单号：{}, 状态：{}", orderSn, payDO.getStatus());
                return "success";
            }

            // 校验金额
            BigDecimal notifyAmount = new BigDecimal(params.get("total_amount"));
//            if (!notifyAmount.equals(payDO.getPayAmount())) {
//                log.error("订单金额不匹配，订单号：{}，通知金额：{}，订单金额：{}", orderSn, notifyAmount, payDO.getPayAmount());
//                // 金额不匹配是严重问题，返回failure让支付宝重试
//                return "failure";
//            }

            try {
                // 处理不同的交易状态
                if ("TRADE_SUCCESS".equals(tradeStatus) || "TRADE_FINISHED".equals(tradeStatus)) {
                    handleTradeSuccess(payDO, params, notifyAmount);
                } else if ("TRADE_CLOSED".equals(tradeStatus)) {
                    handleTradeClosed(payDO);
                }
                return "success";
            } catch (Exception e) {
                log.error("处理支付宝回调业务异常，订单号：{}，异常：", orderSn, e);
                // 业务处理异常，返回failure重试
                return "failure";
            }
        } catch (Exception e) {
            e.printStackTrace();
            return "failure";
        }
    }

    // 处理交易成功流程
    private void handleTradeSuccess(PayDO payDO, Map<String, String> params, BigDecimal notifyAmount){
        payDO.setTradeNo(params.get("trade_no"));
        payDO.setStatus(TradeStatusEnum.TRADE_SUCCESS.tradeCode());
        payDO.setPayAmount(notifyAmount);
        payDO.setGmtPayment(DateUtil.parse(params.get("gmt_payment")));
        String orderSn = params.get("out_trade_no");

        // 如果有优惠券则消费该优惠券
        Long couponTemplateId = payDO.getCouponTemplateId();
        if (couponTemplateId != null){
            LambdaUpdateWrapper<UserCouponDO> updateWrapper = Wrappers.lambdaUpdate(UserCouponDO.class)
                    .eq(UserCouponDO::getUserId, payDO.getUserId())
                    .eq(UserCouponDO::getCouponTemplateId, couponTemplateId)
                    .set(UserCouponDO::getStatus, UserCouponStatusEnum.USED);

            int update = userCouponMapper.update(updateWrapper);
            if (!SqlHelper.retBool(update)){
                log.error("用户ID:{},订单:{}，消费优惠券失败:{}", payDO.getUserId(),orderSn,couponTemplateId);
                throw new ServiceException("优惠券使用失败");
            }
        }
        // 进行订单状态更新
        LambdaUpdateWrapper<PayDO> updateWrapper = Wrappers.lambdaUpdate(PayDO.class)
                .eq(PayDO::getOrderSn, orderSn);
        int update = payMapper.update(payDO, updateWrapper);
        if (!SqlHelper.retBool(update)){
            log.error("修改支付宝订单失败，支付单信息：{}", JSON.toJSONString(payDO));
            throw new ServiceException("订单状态更新失败");
        }
    }

    // 处理交易取消流程
    private void handleTradeClosed(PayDO payDO){
        payDO.setStatus(TradeStatusEnum.TRADE_CLOSED.tradeCode());
        payMapper.updateById(payDO);

        // 解锁优惠券
        Long couponTemplateId = payDO.getCouponTemplateId();
        if (couponTemplateId != null) {
            LambdaUpdateWrapper<UserCouponDO> updateWrapper = Wrappers.lambdaUpdate(UserCouponDO.class)
                    .eq(UserCouponDO::getUserId, payDO.getUserId())
                    .eq(UserCouponDO::getCouponTemplateId, couponTemplateId)
                    .set(UserCouponDO::getStatus, UserCouponStatusEnum.UNUSED);
            int update = userCouponMapper.update(updateWrapper);
            if (!SqlHelper.retBool(update)){
                log.error("优惠券解锁失败，userId：{}，优惠券ID：{}",payDO.getUserId(),couponTemplateId);
            }
        }
    }
}
